/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package edu.isi.pegasus.planner.catalog.replica.impl;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import edu.isi.pegasus.common.util.Escape;
import edu.isi.pegasus.planner.catalog.ReplicaCatalog;
import edu.isi.pegasus.planner.catalog.replica.ReplicaCatalogEntry;

/**
 * This class implements a replica catalog which directly writes to the output
 * file. This is done so that the Cache file generation would not require
 * storing all RC entries in memory.
 * 
 * The implementation does not maintain any internal state, and is not aware if
 * same entries are being written multiple times.
 * 
 * The class using this implementation needs to ensure only valid entries are
 * passed through to the insert method, and that entries are not being
 * duplicated.
 * 
 * @author Rajiv Mayani
 * @version $Revision: 5907 $
 */
public class FlushedCache implements ReplicaCatalog {

	/**
	 * A buffered writer, to buffer all file writes being performed.
	 */
	protected BufferedWriter m_out;

	/**
	 * No. of entries to buffer.
	 */
	private int m_buffer_size = 1000;

	/**
	 * No. of entries to buffer.
	 */
	private static final int m_avg_line_size = 100;

	/**
	 * No. of un-flushed entries.
	 */
	private int m_line_count = 0;

	/**
	 * Set buffer size i.e. The no of lines that the implementation will buffer.
	 */
	public void setBufferSize(int bufferSize) {
		m_buffer_size = bufferSize;
	}

	/**
	 * Opens the file for writing.
	 * 
	 * @param filename
	 *            is the name of the file to write too.
	 * @return true, if the file can be opened for writing
	 */
	public boolean connect(String filename) {
		// sanity check
		if (filename == null)
			return false;

		try {
			// Create a buffered writer with a buffer size of m_buffer_size
			// lines, with each line averaging a m_avg_line_size bytes
			m_out = new BufferedWriter(new FileWriter(filename), m_buffer_size
					* m_avg_line_size);
		} catch (IOException ioe) {
			System.err.println(ioe.getMessage());
			return false;
		}

		return true;
	}

	/**
	 * Establishes a connection to the database from the properties. You will
	 * need to specify a "file" property to point to the location of the on-disk
	 * instance.
	 * 
	 * @param props
	 *            is the property table with sufficient settings to establish a
	 *            link with the database.
	 * @return true if connected, false if failed to connect.
	 * 
	 */
	public boolean connect(Properties props) {

		if (props.containsKey("file"))
			return connect(props.getProperty("file"));

		return false;
	}

	/**
	 * Quotes a string only if necessary. This methods first determines, if a
	 * strings requires quoting, because it contains whitespace, an equality
	 * sign, quotes, or a backslash. If not, the string is not quoted. If the
	 * input contains forbidden characters, it is placed into quotes and quote
	 * and backslash are backslash escaped.
	 * <p>
	 * However, if the property "quote" had a <code>true</code> value when
	 * connecting to the database, output will always be quoted.
	 * 
	 * @param e
	 *            is the Escape instance used to escape strings.
	 * @param s
	 *            is the string that may require quoting
	 * @return either the original string, or a newly allocated instance to an
	 *         escaped string.
	 */
	public String quote(Escape e, String s) {
		String result = null;

		if (s == null || s.length() == 0) {
			// empty string short-cut
			result = s;
		} else {
			// string has content
			boolean flag = false;
			for (int i = 0; i < s.length() && !flag; ++i) {
				// Note: loop will never trigger, if m_quote is true
				char ch = s.charAt(i);
				flag = (ch == '"' || ch == '\\' || ch == '=' || Character
						.isWhitespace(ch));
			}

			result = (flag ? '"' + e.escape(s) + '"' : s);
		}

		// single point of exit
		return result;
	}

	/**
	 * 
	 * The method generate a String representation of the replica catalog entry.
	 * The String representation follows the format expected by SimpleFile
	 * replica catalog implementation.
	 * 
	 * @param lfn
	 *            The logical file name
	 * @param rce
	 *            The replica catalog entry consisting of the physical file name
	 *            name and key, value pairs.
	 * @return A String representation of the replica-catalog entry.
	 */
	protected String writeReplicaCatalogEntry(String lfn,
			ReplicaCatalogEntry rce) {
		Escape e = new Escape("\"\\", '\\');
		StringBuilder s = new StringBuilder();

		s.append(quote(e, lfn));
		s.append(' ');
		s.append(quote(e, rce.getPFN()));

		for (Iterator k = rce.getAttributeIterator(); k.hasNext();) {
			String key = (String) k.next();
			String value = (String) rce.getAttribute(key);
			s.append(' ');
			s.append(key);
			s.append("=\"");
			s.append(e.escape(value));
			s.append('"');
		}

		m_line_count++;
		s.append(System.getProperty("line.separator", "\r\n"));
		return s.toString();
	}

	/**
	 * Check if the buffer is full i.e. No. of unwritten lines is same as
	 * m_buffer_size
	 * 
	 * @see setBufferSize
	 */
	private void flush() {
		if (m_buffer_size - m_line_count == 0) {
			try {
				m_line_count = 0;
				m_out.flush();
			} catch (IOException ioe) {
				System.err.println(ioe.getMessage());
			}
		}
	}

	/**
	 * This operation will dump the in-memory representation back onto disk. The
	 * store operation is strict in what it produces. The LFN and PFN records
	 * are only quoted, if they require quotes, because they contain special
	 * characters. The attributes are <b>always</b> quoted and thus
	 * quote-escaped.
	 */
	public void close() {

		try {
			if (m_out != null) {
				m_out.flush();
			}
		} catch (IOException ioe) {
			System.err.println(ioe.getMessage());
		} finally {

			if (m_out != null) {
				try {
					if (m_out != null) {
						m_out.close();
					}
				} catch (IOException ioe) {
					System.err.println(ioe.getMessage());
				}

				m_out = null;
			}
		}
	}

	/**
	 * Predicate to check, if the connection with the catalog's implementation
	 * is still active. This helps determining, if it makes sense to call
	 * <code>close()</code>.
	 * 
	 * @return true, if the implementation is disassociated, false otherwise.
	 * @see #close()
	 */
	public boolean isClosed() {
		return (m_out == null);
	}

	public String lookup(String lfn, String handle) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public Collection lookup(String lfn) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public Set lookupNoAttributes(String lfn) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public Map lookup(Set lfns) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public Map lookupNoAttributes(Set lfns) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public Map lookup(Set lfns, String handle) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public Map lookupNoAttributes(Set lfns, String handle) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public Map lookup(Map constraints) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public Set list() {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public Set list(String constraint) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	/**
	 * Inserts a new mapping into the replica catalog. Any existing mapping of
	 * the same LFN, PFN, and HANDLE will be replaced, including all of its
	 * attributes.
	 * 
	 * @param lfn
	 *            is the logical filename under which to book the entry.
	 * @param tuple
	 *            is the physical filename and associated PFN attributes.
	 * 
	 * @return number of insertions, should always be 1. On failure, throw an
	 *         exception, don't use zero.
	 */
	public int insert(String lfn, ReplicaCatalogEntry tuple) {
		if (lfn == null || tuple == null)
			throw new NullPointerException();

		write(writeReplicaCatalogEntry(lfn, tuple));

		return 1;
	}

	/**
	 * Inserts a new mapping into the replica catalog. This is a convenience
	 * function exposing the resource handle. Internally, the
	 * <code>ReplicaCatalogEntry</code> element will be constructed, and passed
	 * to the appropriate insert function.
	 * 
	 * @param lfn
	 *            is the logical filename under which to book the entry.
	 * @param pfn
	 *            is the physical filename associated with it.
	 * @param handle
	 *            is a resource handle where the PFN resides.
	 * @return number of insertions, should always be 1. On failure, throw an
	 *         exception, don't use zero.
	 * @see #insert(String, ReplicaCatalogEntry )
	 * @see ReplicaCatalogEntry
	 */
	public int insert(String lfn, String pfn, String handle) {
		if (lfn == null || pfn == null || handle == null)
			throw new NullPointerException();

		write(writeReplicaCatalogEntry(lfn,
				new ReplicaCatalogEntry(pfn, handle)));
		return 1;
	}

	/**
	 * Inserts multiple mappings into the replica catalog. The input is a map
	 * indexed by the LFN. The value for each LFN key is a collection of replica
	 * catalog entries. Note that this operation will not replace existing
	 * entries.
	 * 
	 * @param x
	 *            is a map from logical filename string to list of replica
	 *            catalog entries.
	 * @return the number of insertions.
	 * @see org.griphyn.common.catalog.ReplicaCatalogEntry
	 */
	public int insert(Map x) {
		int result = 0;

		// shortcut sanity
		if (x == null || x.isEmpty())
			return result;

		for (Iterator i = x.keySet().iterator(); i.hasNext();) {
			String lfn = (String) i.next();
			Object val = x.get(lfn);
			if (val instanceof ReplicaCatalogEntry) {

				write(writeReplicaCatalogEntry(lfn, (ReplicaCatalogEntry) val));
			} else {
				// this is how it should have been
				for (Iterator j = ((Collection) val).iterator(); j.hasNext();) {
					ReplicaCatalogEntry rce = (ReplicaCatalogEntry) j.next();
					write(writeReplicaCatalogEntry(lfn,
							(ReplicaCatalogEntry) val));
				}
			}
		}

		return result;
	}

	private void write(String content) {
		try {
			m_out.write(content);
			flush();
		} catch (IOException ioe) {
			System.err.println(ioe.getMessage());
		}
	}

	public int delete(String lfn, String pfn) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public int delete(Map x, boolean matchAttributes) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public int delete(String lfn, ReplicaCatalogEntry tuple) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public int delete(String lfn, String name, Object value) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public int deleteByResource(String lfn, String handle) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public int remove(String lfn) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public int remove(Set lfns) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public int removeByAttribute(String name, Object value) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public int removeByAttribute(String handle) {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}

	public int clear() {
		throw new java.lang.UnsupportedOperationException(
				"Method not implemented");
	}
}
